package com.virus.server.catalina;

import com.virus.server.classloader.AppBaseClassLoader;
import com.virus.server.http.ApplicationContext;
import com.virus.server.utils.ContextXmlUtils;
import com.virus.server.utils.FileUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import javax.servlet.*;
import java.io.File;
import java.io.IOException;
import java.util.*;

public class Context {

    private String path;

    private String docBose;

    private Map<String, String> urlPattern2ServletClass;

    private Map<String, String> servletName2UrlPattern;

    private Map<String, String> servletName2ServletClass;

    private AppBaseClassLoader appBaseClassLoader;

    private ServletContext servletContext;

    private Map<Class, Servlet> servletPool;

    private Map<String, String> filterName2FilterClass;

    private Map<String, String> filterName2UrlPattern;

    private Map<String, List<String>> urlPattern2FilterClass;

    private Map<String, Filter> filterPool;

    private List<ServletContextListener> listeners;

    public Context(String path, String docBose) {
        this.path = path;
        this.docBose = docBose;
        this.urlPattern2ServletClass = new HashMap<>();
        this.servletName2UrlPattern = new HashMap<>();
        this.servletName2ServletClass = new HashMap<>();
        this.servletPool = new HashMap<>();
        this.filterName2FilterClass = new HashMap<>();
        this.filterName2UrlPattern = new HashMap<>();
        this.urlPattern2FilterClass = new HashMap<>();
        this.filterPool = new HashMap<>();
        this.listeners = new ArrayList<>();
        this.servletContext = new ApplicationContext(this);
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        this.appBaseClassLoader = new AppBaseClassLoader(this.docBose, contextClassLoader);
        deploy();
    }

    private void deploy() {
        File file = new File(docBose, ContextXmlUtils.parseContext());
        String webXml = null;
        try {
            if (!file.exists()) {
                return;
            }
            webXml = FileUtils.readUTF8(file);
        } catch (IOException e) {
            e.printStackTrace();
        }
        Document document = Jsoup.parse(webXml);
        deploy(document);
    }

    private void deploy(Document document) {
        deployServlet(document);
        deployFilter(document);
        deployListener(document);
    }

    private void deployServlet(Document document) {
        setServletMap(document);
        checkAndSetMap();
        parseLoadOnStartUp(document);
    }

    private void deployFilter(Document document) {
        setFilterMap(document);
        setFilterMap();
        Set<String> keySet = filterName2FilterClass.keySet();
        for (String key : keySet) {
            String filterClass = filterName2FilterClass.get(key);
            getFilter(filterClass);
        }
    }

    private void deployListener(Document document) {
        setList(document);
        initListener();
    }

    private void setServletMap(Document document) {
        Elements servletElements = document.select("servlet");
        for (Element servletElement : servletElements) {
            String servletName = servletElement.select("servlet-name").text();
            String servletClass = servletElement.select("servlet-class").text();
            servletName2ServletClass.put(servletName, servletClass);
        }
        Elements servletMappingElements = document.select("servlet-mapping");
        for (Element servletMappingElement : servletMappingElements) {
            String servletName = servletMappingElement.select("servlet-name").text();
            String urlPattern = servletMappingElement.select("url-pattern").text();
            servletName2UrlPattern.put(servletName, urlPattern);
        }
    }

    private void setFilterMap(Document document) {
        Elements FilterElements = document.select("filter");
        for (Element servletElement : FilterElements) {
            String filterName = servletElement.select("filter-name").text();
            String filterClass = servletElement.select("filter-class").text();
            filterName2FilterClass.put(filterName, filterClass);
        }
        Elements filterMappingElements = document.select("filter-mapping");
        for (Element servletMappingElement : filterMappingElements) {
            String filterName = servletMappingElement.select("filter-name").text();
            String urlPattern = servletMappingElement.select("url-pattern").text();
            filterName2UrlPattern.put(filterName, urlPattern);
        }
    }

    private void checkAndSetMap() {

        Set<String> keySet = servletName2UrlPattern.keySet();
        for (String key : keySet) {
            String urlPattern = servletName2UrlPattern.get(key);
            String servletClass = servletName2ServletClass.get(key);
            if (servletClass == null) {
                throw new IllegalArgumentException("the servlet name [" + key + "] does not have a servlet class");
            }
            if (urlPattern2ServletClass.get(urlPattern) != null) {
                throw new IllegalArgumentException("the servlet class named " + servletClass + " and " + urlPattern2ServletClass.get(urlPattern) + " are both mapped to the url pattern which is not permitted");
            }
            urlPattern2ServletClass.put(urlPattern, servletClass);
        }
    }

    private void setFilterMap() {
        Set<String> keySet = filterName2UrlPattern.keySet();
        for (String key : keySet) {
            String urlPattern = filterName2UrlPattern.get(key);
            String filterClass = filterName2FilterClass.get(key);
            if (filterClass == null) {
                throw new IllegalArgumentException("the filter name [" + key + "] does not have a filter class");
            }
            List<String> filters = urlPattern2FilterClass.get(urlPattern);
            if (filters == null) {
                filters = new ArrayList<>();
                urlPattern2FilterClass.put(urlPattern,filters);
            }
            filters.add(filterClass);
        }
    }

    public synchronized Filter getFilter(String filterClass) {
        Filter filter = filterPool.get(filterClass);
        if (filter == null){
            Class<?> aClass = null;
            try {
                aClass = appBaseClassLoader.loadClass(filterClass);
                if (aClass == null) {
                    throw new IllegalArgumentException("Filter [" + filterClass + "]does not exist");
                }
                filter = (Filter) aClass.newInstance();
                filter.init(null);
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | ServletException e) {
                e.printStackTrace();
            }
            filterPool.put(filterClass,filter);
        }
        return filter;
    }

    private void parseLoadOnStartUp(Document document) {
        Queue<String> priorQueue = new PriorityQueue<>(new Comparator<String>() {
            @Override
            public int compare(String s1, String s2) {
                int i = s1.indexOf(":");
                int j = s2.indexOf(":");
                String substring1 = s1.substring(i + 1);
                String substring2 = s2.substring(j + 1);
                int ii = Integer.parseInt(substring1);
                int jj = Integer.parseInt(substring2);
                return ii - jj;
            }
        });
        Elements servletElements = document.select("servlet");
        for (Element servletElement : servletElements) {
            String servletClass = servletElement.select("servlet-class").first().text();
            Elements loadOnStartUpElement = servletElement.select("load-on-startup");
            if (loadOnStartUpElement.size() != 0){
                String loadOnStartUpString = loadOnStartUpElement.first().text();
                if (Integer.parseInt(loadOnStartUpString) >= 0) {
                    String s = servletClass + ":" + loadOnStartUpString;
                    priorQueue.offer(s);
                }
            }
        }
        loadServlet(priorQueue);
    }

    private void loadServlet(Queue<String> priorQueue) {
        while (!priorQueue.isEmpty()) {
            String poll = priorQueue.poll();
            int i = poll.indexOf(":");
            String clz = poll.substring(0, i);
            try {
                Class<?> aClass = appBaseClassLoader.loadClass(clz);
                Servlet servlet = (Servlet) aClass.newInstance();
                servlet.init(null);
                this.servletPool.put(aClass, servlet);
            } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | ServletException e) {
                e.printStackTrace();
            }
        }
    }

    private void setList(Document document) {
        Elements elements = document.select("listener listener-class");
        if (elements == null) {
            return;
        }
        for (Element element : elements) {
            String text = element.text();
            try {
                Class<?> aClass = appBaseClassLoader.loadClass(text);
                ServletContextListener listener = (ServletContextListener) aClass.newInstance();
                listeners.add(listener);
            } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
                e.printStackTrace();
            }
        }
    }

    private void initListener() {
        ServletContextEvent servletContextEvent = new ServletContextEvent(servletContext);
        if (listeners.isEmpty()) {
            return;
        }
        for (ServletContextListener listener : listeners) {
            listener.contextInitialized(servletContextEvent);
        }
    }

    public String getPath() {
        return path;
    }

    public String getDocBose() {
        return docBose;
    }

    public String getServletClass(String urlPattern) {
        return urlPattern2ServletClass.get(urlPattern);
    }

    public AppBaseClassLoader getAppBaseClassLoader() {
        return appBaseClassLoader;
    }

    public ServletContext getServletContext() {
        return servletContext;
    }

    public Servlet getServlet(Class<?> c) {
        Servlet servlet = this.servletPool.get(c);
        if (servlet == null) {
            try {
                servlet = (Servlet) c.newInstance();
                servlet.init(null);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ServletException e) {
                e.printStackTrace();
            }
            this.servletPool.put(c, servlet);
        }
        return servlet;
    }

    public List<Filter> getFilters(String requestURI) {
        List<Filter> filters = new ArrayList<>();
        List<String> filterClass = urlPattern2FilterClass.get(requestURI);
        if (filterClass == null) {
            return null;
        }
        for (String aClass : filterClass) {
            Filter filter = filterPool.get(aClass);
            filters.add(filter);
        }
        return filters;
    }
}
